unit uNumberStub;

interface

uses uNumber;

type

  TNumberStub = class(TNumber)
  private
    FValue: TNumberBase;

    function  GetValue: TNumberBase;
    procedure SetValue(AValue: TNumberBase);

  public
    constructor Create; overload;
    constructor Create(AValue: TNumberBase); overload;

    function  CreateNumberOfSameType: TNumber; override;

    function  Abs: TNumberBase; override;
    procedure Add(ANumber: TNumber); override;
    procedure BecomeAdditiveIdentity; override;
    procedure BecomeMultiplicativeIdentity; override;
    procedure CopyFrom(ANumber: TNumber); override;
    procedure Clear; override;
    procedure ChangeSign; override;
    procedure DivideBy(ANumber: TNumber); override;
    procedure FromString(const AString: String); override;
    procedure Invert; override;
    function  IsAdditiveIdentity:boolean; override;
    function  IsMultiplicativeIdentity:boolean; override;
    procedure MultiplyBy(ANumber: TNumber); override;
    procedure Random; override;
    procedure Subtract(ANumber: TNumber); override;
    function  ToString(const AFormat: string = DEFAULT_NUMBER_FORMAT): string; override;

    procedure Power(AExponent: TNumber); override;
    procedure MakeFactorial; override; 

    property  Value:TNumberBase read GetValue write SetValue;
  end;

implementation

uses SysUtils, Math, uFunctions, uNumberExceptions;

{TNumberStub}

constructor TNumberStub.Create;
begin
  inherited;
  FValue := 0;
end;

constructor TNumberStub.Create(AValue: TNumberBase);
begin
  inherited Create;
  FValue := AValue;
end;

function TNumberStub.CreateNumberOfSameType: TNumber;
begin
  Result := TNumberStub.Create;
end;

function TNumberStub.Abs: TNumberBase;
begin
  result:=system.Abs(FValue);
end;

procedure TNumberStub.Add(ANumber: TNumber);
begin
  if (not self.Valid) or (not ANumber.IsValid) then
  begin
    raise ECalculatingWithInvalidNumbers.Create('Calculating with invalid numbers');
  end;

  if (ANumber is TNumberStub) then
    FValue := FValue + TNumberStub(ANumber).FValue
  else
    raise EIncompatibleNumberTypes.Create(GetIncompatibleNumberTypesMsg(ANumber));
end;

procedure TNumberStub.BecomeAdditiveIdentity;
begin
  FValue := 0;
  Valid := true;
end;

procedure TNumberStub.BecomeMultiplicativeIdentity;
begin
  FValue := 1;
  Valid := true;
end;

procedure TNumberStub.ChangeSign;
begin
  FValue := -1*FValue;
end;

procedure TNumberStub.Clear;
begin
  FValue := 0;
  Valid := true;
end;

procedure TNumberStub.CopyFrom(ANumber: TNumber);
begin
  if (ANumber is TNumberStub) then
    FValue := TNumberStub(ANumber).FValue
  else
    raise EIncompatibleNumberTypes.Create(GetIncompatibleNumberTypesMsg(ANumber));
end;

procedure TNumberStub.DivideBy(ANumber: TNumber);
begin
  if (not self.Valid) or (not ANumber.IsValid) then
  begin
    raise ECalculatingWithInvalidNumbers.Create('Calculating with invalid numbers');
  end;

  if ANumber is TNumberStub then
  begin
    if TNumberStub(ANumber).FValue <> 0 then
      FValue := FValue / TNumberStub(ANumber).FValue
    else
      raise EDivisionByZero.Create('Division by zero');
  end
  else begin
    raise EIncompatibleNumberTypes.Create(GetIncompatibleNumberTypesMsg(ANumber));
  end;

end;

procedure TNumberStub.FromString(const AString: String);
begin
  try
    FValue := StrToFloat(AString);
    self.Valid := true;
  except
    self.Valid := false;
    raise EInvalidStringValue.CreateFmt('"%s" is not a valid TNumberStub value',[AString]);
  end;
end;

function TNumberStub.GetValue: TNumberBase;
begin
  result := FValue;
end;

procedure TNumberStub.Invert;
begin
  if IsAdditiveIdentity then
  begin
    raise EDivisionByZero.Create('Cannot invert an addition neutral element (additive identity)');
  end;

  FValue := 1 / FValue;
end;

function TNumberStub.IsAdditiveIdentity: boolean;
begin
  result := FValue = 0;
end;

function TNumberStub.IsMultiplicativeIdentity: boolean;
begin
  result := FValue = 1;
end;

procedure TNumberStub.MakeFactorial;
var
  i, max: Cardinal;
begin
  if not self.Valid then
  begin
    raise ECalculatingWithInvalidNumbers.Create('Calculating with invalid numbers');
  end;

  if (FValue - round(FValue) <> 0) or (FValue > 40) then
  begin
    FValue := Gamma(FValue + 1)
  end
  else
  begin
    max := Round(FValue);
    FValue := 1;
    for i := 2 to max do
      FValue := FValue * i;
  end;
end;

procedure TNumberStub.MultiplyBy(ANumber: TNumber);
begin
  if (not self.Valid) or (not ANumber.IsValid) then
  begin
    raise ECalculatingWithInvalidNumbers.Create('Calculating with invalid numbers');
  end;

  if (ANumber is TNumberStub) then
    FValue := FValue * TNumberStub(ANumber).FValue
  else
    raise EIncompatibleNumberTypes.Create(GetIncompatibleNumberTypesMsg(ANumber));
end;

procedure TNumberStub.Power(AExponent: TNumber);
begin
  if (not Self.Valid) or (not AExponent.IsValid) then
  begin
    raise ECalculatingWithInvalidNumbers.Create('Calculating with invalid numbers');
  end;

  if (AExponent is TNumberStub) then
    FValue := Math.Power(FValue, TNumberStub(AExponent).FValue)
  else
    raise EIncompatibleNumberTypes.Create(GetIncompatibleNumberTypesMsg(AExponent));
end;

procedure TNumberStub.Random;
begin
  FValue := system.random;
end;

procedure TNumberStub.SetValue(AValue: TNumberBase);
begin
  FValue := AValue;
end;

procedure TNumberStub.Subtract(ANumber: TNumber);
begin
  if (not self.Valid) or (not ANumber.IsValid) then
  begin
    raise ECalculatingWithInvalidNumbers.Create('Calculating with invalid numbers');
  end;

  if (ANumber is TNumberStub) then
    FValue := FValue - TNumberStub(ANumber).FValue
  else
    raise EIncompatibleNumberTypes.Create(GetIncompatibleNumberTypesMsg(ANumber));
end;

function TNumberStub.ToString(const AFormat: String = DEFAULT_NUMBER_FORMAT): String;
begin
  if self.Valid then
    result := FormatFloat(AFormat,FValue)
  else
    result := 'NaN';
end;

end.
